{
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "# Scalable Constrained Bayesian Optimization (SCBO)\n",
        "In this tutorial, we show how to implement Scalable Constrained Bayesian Optimization (SCBO) [1] in a closed loop in BoTorch.\n",
        "\n",
        "We optimize the 10𝐷 Ackley function on the domain $[−5,10]^{10}$. This implementation uses two simple constraint functions $c1$ and $c2$. Our goal is to find an $x$ that maximizes the Ackley function subject to the constraints $c1(x) \\leq 0$ and $c2(x) \\leq 0$.\n",
        "\n",
        "[1]: David Eriksson and Matthias Poloczek. Scalable constrained Bayesian optimization. In International Conference on Artificial Intelligence and Statistics, pages 730–738. PMLR, 2021.\n",
        "(https://doi.org/10.48550/arxiv.2002.08526)\n",
        "\n",
        "Since SCBO is essentially a constrained version of Trust Region Bayesian Optimization (TuRBO), this tutorial shares much of the same code as the TuRBO Tutorial (https://botorch.org/docs/tutorials/turbo_1) with small modifications made to implement SCBO."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "# Install dependencies if we are running in colab\n",
        "import sys\n",
        "if 'google.colab' in sys.modules:\n",
        "    %pip install botorch"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": [
        "import math\n",
        "import os\n",
        "import warnings\n",
        "from dataclasses import dataclass\n",
        "\n",
        "import gpytorch\n",
        "import torch\n",
        "from gpytorch.constraints import Interval\n",
        "from gpytorch.kernels import MaternKernel, ScaleKernel\n",
        "from gpytorch.likelihoods import GaussianLikelihood\n",
        "from gpytorch.mlls import ExactMarginalLogLikelihood\n",
        "from torch import Tensor\n",
        "from torch.quasirandom import SobolEngine\n",
        "\n",
        "from botorch.fit import fit_gpytorch_mll\n",
        "# Constrained Max Posterior Sampling s a new sampling class, similar to MaxPosteriorSampling,\n",
        "# which implements the constrained version of Thompson Sampling described in [1].\n",
        "from botorch.generation.sampling import ConstrainedMaxPosteriorSampling\n",
        "from botorch.models import SingleTaskGP\n",
        "from botorch.models.model_list_gp_regression import ModelListGP\n",
        "from botorch.test_functions import Ackley\n",
        "from botorch.utils.transforms import unnormalize\n",
        "\n",
        "warnings.filterwarnings(\"ignore\")\n",
        "\n",
        "device = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\n",
        "dtype = torch.double\n",
        "tkwargs = {\"device\": device, \"dtype\": dtype}\n",
        "\n",
        "SMOKE_TEST = os.environ.get(\"SMOKE_TEST\")"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Demonstration with 10-dimensional Ackley function and Two Simple Constraint Functions"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 2,
      "metadata": {},
      "outputs": [],
      "source": [
        "# Here we define the example 10D Ackley function\n",
        "fun = Ackley(dim=10, negate=True).to(**tkwargs)\n",
        "fun.bounds[0, :].fill_(-5)\n",
        "fun.bounds[1, :].fill_(10)\n",
        "dim = fun.dim\n",
        "lb, ub = fun.bounds\n",
        "\n",
        "batch_size = 4\n",
        "n_init = 10\n",
        "max_cholesky_size = float(\"inf\")  # Always use Cholesky\n",
        "\n",
        "# When evaluating the function, we must first unnormalize the inputs since\n",
        "# we will use normalized inputs x in the main optimizaiton loop\n",
        "def eval_objective(x):\n",
        "    \"\"\"This is a helper function we use to unnormalize and evalaute a point\"\"\"\n",
        "    return fun(unnormalize(x, fun.bounds))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "### Defining two simple constraint functions\n",
        "\n",
        "#### We'll use two constraints functions: c1 and c2 \n",
        "We want to find solutions which maximize the above Ackley objective subject to the constraint that \n",
        "c1(x) <= 0 and c2(x) <= 0 \n",
        "Note that SCBO expects all constraints to be of the for c(x) <= 0, so any other desired constraints must be modified to fit this form. \n",
        "\n",
        "Note also that while the below constraints are very simple functions, the point of this tutorial is to show how to use SCBO, and this same implementation could be applied in the same way if c1, c2 were actually complex black-box functions. \n"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 3,
      "metadata": {},
      "outputs": [],
      "source": [
        "def c1(x):  # Equivalent to enforcing that sum(x) <= 0\n",
        "    return x.sum()\n",
        "\n",
        "\n",
        "def c2(x):  # Equivalent to enforcing that ||x||_2 <= 5\n",
        "    return torch.norm(x, p=2) - 5\n",
        "\n",
        "\n",
        "# We assume c1, c2 have same bounds as the Ackley function above\n",
        "def eval_c1(x):\n",
        "    \"\"\"This is a helper function we use to unnormalize and evalaute a point\"\"\"\n",
        "    return c1(unnormalize(x, fun.bounds))\n",
        "\n",
        "\n",
        "def eval_c2(x):\n",
        "    \"\"\"This is a helper function we use to unnormalize and evalaute a point\"\"\"\n",
        "    return c2(unnormalize(x, fun.bounds))"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Define TuRBO Class\n",
        "\n",
        "Just as in the TuRBO Tutorial (https://botorch.org/docs/tutorials/turbo_1), we'll define a class to hold the turst region state and a method update_state() to update the side length of the trust region hyper-cube during optimization. We'll update the side length according to the number of sequential successes or failures as discussed in the original TuRBO paper. "
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 4,
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "ScboState(dim=10, batch_size=4, length=0.8, length_min=0.0078125, length_max=1.6, failure_counter=0, failure_tolerance=3, success_counter=0, success_tolerance=10, best_value=-inf, best_constraint_values=tensor([inf, inf], dtype=torch.float64), restart_triggered=False)\n"
          ]
        }
      ],
      "source": [
        "@dataclass\n",
        "class ScboState:\n",
        "    dim: int\n",
        "    batch_size: int\n",
        "    length: float = 0.8\n",
        "    length_min: float = 0.5**7\n",
        "    length_max: float = 1.6\n",
        "    failure_counter: int = 0\n",
        "    failure_tolerance: int = float(\"nan\")  # Note: Post-initialized\n",
        "    success_counter: int = 0\n",
        "    success_tolerance: int = 10  # Note: The original paper uses 3\n",
        "    best_value: float = -float(\"inf\")\n",
        "    best_constraint_values: Tensor = torch.ones(2, **tkwargs) * torch.inf\n",
        "    restart_triggered: bool = False\n",
        "\n",
        "    def __post_init__(self):\n",
        "        self.failure_tolerance = math.ceil(max([4.0 / self.batch_size, float(self.dim) / self.batch_size]))\n",
        "\n",
        "\n",
        "def update_tr_length(state: ScboState):\n",
        "    # Update the length of the trust region according to\n",
        "    # success and failure counters\n",
        "    # (Just as in original TuRBO paper)\n",
        "    if state.success_counter == state.success_tolerance:  # Expand trust region\n",
        "        state.length = min(2.0 * state.length, state.length_max)\n",
        "        state.success_counter = 0\n",
        "    elif state.failure_counter == state.failure_tolerance:  # Shrink trust region\n",
        "        state.length /= 2.0\n",
        "        state.failure_counter = 0\n",
        "\n",
        "    if state.length < state.length_min:  # Restart when trust region becomes too small\n",
        "        state.restart_triggered = True\n",
        "\n",
        "    return state\n",
        "\n",
        "\n",
        "def get_best_index_for_batch(Y: Tensor, C: Tensor):\n",
        "    \"\"\"Return the index for the best point.\"\"\"\n",
        "    is_feas = (C <= 0).all(dim=-1)\n",
        "    if is_feas.any():  # Choose best feasible candidate\n",
        "        score = Y.clone()\n",
        "        score[~is_feas] = -float(\"inf\")\n",
        "        return score.argmax()\n",
        "    return C.clamp(min=0).sum(dim=-1).argmin()\n",
        "\n",
        "\n",
        "def update_state(state, Y_next, C_next):\n",
        "    \"\"\"Method used to update the TuRBO state after each step of optimization.\n",
        "\n",
        "    Success and failure counters are updated according to the objective values\n",
        "    (Y_next) and constraint values (C_next) of the batch of candidate points\n",
        "    evaluated on the optimization step.\n",
        "\n",
        "    As in the original TuRBO paper, a success is counted whenver any one of the\n",
        "    new candidate points improves upon the incumbent best point. The key difference\n",
        "    for SCBO is that we only compare points by their objective values when both points\n",
        "    are valid (meet all constraints). If exactly one of the two points being compared\n",
        "    violates a constraint, the other valid point is automatically considered to be better.\n",
        "    If both points violate some constraints, we compare them inated by their constraint values.\n",
        "    The better point in this case is the one with minimum total constraint violation\n",
        "    (the minimum sum of constraint values)\"\"\"\n",
        "\n",
        "    # Pick the best point from the batch\n",
        "    best_ind = get_best_index_for_batch(Y=Y_next, C=C_next)\n",
        "    y_next, c_next = Y_next[best_ind], C_next[best_ind]\n",
        "\n",
        "    if (c_next <= 0).all():\n",
        "        # At least one new candidate is feasible\n",
        "        improvement_threshold = state.best_value + 1e-3 * math.fabs(state.best_value)\n",
        "        if y_next > improvement_threshold or (state.best_constraint_values > 0).any():\n",
        "            state.success_counter += 1\n",
        "            state.failure_counter = 0\n",
        "            state.best_value = y_next.item()\n",
        "            state.best_constraint_values = c_next\n",
        "        else:\n",
        "            state.success_counter = 0\n",
        "            state.failure_counter += 1\n",
        "    else:\n",
        "        # No new candidate is feasible\n",
        "        total_violation_next = c_next.clamp(min=0).sum(dim=-1)\n",
        "        total_violation_center = state.best_constraint_values.clamp(min=0).sum(dim=-1)\n",
        "        if total_violation_next < total_violation_center:\n",
        "            state.success_counter += 1\n",
        "            state.failure_counter = 0\n",
        "            state.best_value = y_next.item()\n",
        "            state.best_constraint_values = c_next\n",
        "        else:\n",
        "            state.success_counter = 0\n",
        "            state.failure_counter += 1\n",
        "\n",
        "    # Update the length of the trust region according to the success and failure counters\n",
        "    state = update_tr_length(state)\n",
        "    return state\n",
        "\n",
        "\n",
        "# Define example state\n",
        "state = ScboState(dim=dim, batch_size=batch_size)\n",
        "print(state)"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "### Generate Initial Points\n",
        "\n",
        "Here we define a simple method to generate a set of random initial datapoints that we will use to kick-off optimization. "
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 5,
      "metadata": {},
      "outputs": [],
      "source": [
        "def get_initial_points(dim, n_pts, seed=0):\n",
        "    sobol = SobolEngine(dimension=dim, scramble=True, seed=seed)\n",
        "    X_init = sobol.draw(n=n_pts).to(dtype=dtype, device=device)\n",
        "    return X_init"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "### Generating a batch of candidates for SCBO \n",
        "\n",
        "Just as in the TuRBO Tutorial (https://botorch.org/docs/tutorials/turbo_1), we'll define a method generate_batch to generate a new batch of candidate points within the TuRBO trust region using Thompson sampling. \n",
        "\n",
        "The key difference here from TuRBO is that, instead of using MaxPosteriorSampling to simply grab the candidates within the trust region with the maximum posterior values, we use ConstrainedMaxPosteriorSampling to instead grab the candidates within the trust region with the maximum posterior values subject to the constraint that the posteriors for the constraint models for c1(x) and c2(x) must be less than or equal to 0 for both candidates. \n",
        "\n",
        "We use additional GPs ('constraint models') to model each black-box constraint (c1 and c2), and throw out all candidates for which the sampled value for these constraint models is greater than 0. According to [1], in the special case when all of the candidaates are predicted to be constraint violators, we select the candidate with the minimum predicted violation. (See botorch.generation.sampling.ConstrainedMaxPosteriorSampling for implementation details)."
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 6,
      "metadata": {},
      "outputs": [],
      "source": [
        "def generate_batch(\n",
        "    state,\n",
        "    model,  # GP model\n",
        "    X,  # Evaluated points on the domain [0, 1]^d\n",
        "    Y,  # Function values\n",
        "    C,  # Constraint values\n",
        "    batch_size,\n",
        "    n_candidates,  # Number of candidates for Thompson sampling\n",
        "    constraint_model,\n",
        "    sobol: SobolEngine,\n",
        "):\n",
        "    assert X.min() >= 0.0 and X.max() <= 1.0 and torch.all(torch.isfinite(Y))\n",
        "\n",
        "    # Create the TR bounds\n",
        "    best_ind = get_best_index_for_batch(Y=Y, C=C)\n",
        "    x_center = X[best_ind, :].clone()\n",
        "    tr_lb = torch.clamp(x_center - state.length / 2.0, 0.0, 1.0)\n",
        "    tr_ub = torch.clamp(x_center + state.length / 2.0, 0.0, 1.0)\n",
        "\n",
        "    # Thompson Sampling w/ Constraints (SCBO)\n",
        "    dim = X.shape[-1]\n",
        "    pert = sobol.draw(n_candidates).to(dtype=dtype, device=device)\n",
        "    pert = tr_lb + (tr_ub - tr_lb) * pert\n",
        "\n",
        "    # Create a perturbation mask\n",
        "    prob_perturb = min(20.0 / dim, 1.0)\n",
        "    mask = torch.rand(n_candidates, dim, **tkwargs) <= prob_perturb\n",
        "    ind = torch.where(mask.sum(dim=1) == 0)[0]\n",
        "    mask[ind, torch.randint(0, dim - 1, size=(len(ind),), device=device)] = 1\n",
        "\n",
        "    # Create candidate points from the perturbations and the mask\n",
        "    X_cand = x_center.expand(n_candidates, dim).clone()\n",
        "    X_cand[mask] = pert[mask]\n",
        "\n",
        "    # Sample on the candidate points using Constrained Max Posterior Sampling\n",
        "    constrained_thompson_sampling = ConstrainedMaxPosteriorSampling(\n",
        "        model=model, constraint_model=constraint_model, replacement=False\n",
        "    )\n",
        "    with torch.no_grad():\n",
        "        X_next = constrained_thompson_sampling(X_cand, num_samples=batch_size)\n",
        "\n",
        "    return X_next"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "## Main Optimization Loop"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 7,
      "metadata": {},
      "outputs": [
        {
          "name": "stdout",
          "output_type": "stream",
          "text": [
            "14) No feasible point yet! Smallest total violation: 1.61e+01, TR length: 8.00e-01\n",
            "18) No feasible point yet! Smallest total violation: 8.45e+00, TR length: 8.00e-01\n",
            "22) No feasible point yet! Smallest total violation: 2.11e+00, TR length: 8.00e-01\n",
            "26) No feasible point yet! Smallest total violation: 2.11e+00, TR length: 8.00e-01\n",
            "30) No feasible point yet! Smallest total violation: 2.11e+00, TR length: 8.00e-01\n",
            "34) No feasible point yet! Smallest total violation: 2.11e+00, TR length: 4.00e-01\n",
            "38) No feasible point yet! Smallest total violation: 8.84e-01, TR length: 4.00e-01\n",
            "42) Best value: -6.17e+00, TR length: 4.00e-01\n",
            "46) Best value: -6.17e+00, TR length: 4.00e-01\n",
            "50) Best value: -5.94e+00, TR length: 4.00e-01\n",
            "54) Best value: -5.94e+00, TR length: 4.00e-01\n",
            "58) Best value: -5.81e+00, TR length: 4.00e-01\n",
            "62) Best value: -4.58e+00, TR length: 4.00e-01\n",
            "66) Best value: -4.58e+00, TR length: 4.00e-01\n",
            "70) Best value: -4.58e+00, TR length: 4.00e-01\n",
            "74) Best value: -4.58e+00, TR length: 2.00e-01\n",
            "78) Best value: -4.19e+00, TR length: 2.00e-01\n",
            "82) Best value: -2.97e+00, TR length: 2.00e-01\n",
            "86) Best value: -2.97e+00, TR length: 2.00e-01\n",
            "90) Best value: -2.97e+00, TR length: 2.00e-01\n",
            "94) Best value: -2.97e+00, TR length: 1.00e-01\n",
            "98) Best value: -2.41e+00, TR length: 1.00e-01\n",
            "102) Best value: -2.41e+00, TR length: 1.00e-01\n",
            "106) Best value: -2.41e+00, TR length: 1.00e-01\n",
            "110) Best value: -2.36e+00, TR length: 1.00e-01\n",
            "114) Best value: -2.36e+00, TR length: 1.00e-01\n",
            "118) Best value: -2.36e+00, TR length: 1.00e-01\n",
            "122) Best value: -2.36e+00, TR length: 5.00e-02\n",
            "126) Best value: -1.57e+00, TR length: 5.00e-02\n",
            "130) Best value: -1.57e+00, TR length: 5.00e-02\n",
            "134) Best value: -1.16e+00, TR length: 5.00e-02\n",
            "138) Best value: -1.16e+00, TR length: 5.00e-02\n",
            "142) Best value: -1.16e+00, TR length: 5.00e-02\n",
            "146) Best value: -1.05e+00, TR length: 5.00e-02\n",
            "150) Best value: -1.05e+00, TR length: 5.00e-02\n",
            "154) Best value: -1.05e+00, TR length: 5.00e-02\n",
            "158) Best value: -1.05e+00, TR length: 2.50e-02\n",
            "162) Best value: -4.22e-01, TR length: 2.50e-02\n",
            "166) Best value: -4.22e-01, TR length: 2.50e-02\n",
            "170) Best value: -4.22e-01, TR length: 2.50e-02\n",
            "174) Best value: -4.22e-01, TR length: 1.25e-02\n",
            "178) Best value: -3.24e-01, TR length: 1.25e-02\n",
            "182) Best value: -3.24e-01, TR length: 1.25e-02\n",
            "186) Best value: -3.24e-01, TR length: 1.25e-02\n",
            "190) Best value: -3.24e-01, TR length: 6.25e-03\n"
          ]
        }
      ],
      "source": [
        "# Generate initial data\n",
        "train_X = get_initial_points(dim, n_init)\n",
        "train_Y = torch.tensor([eval_objective(x) for x in train_X], **tkwargs).unsqueeze(-1)\n",
        "C1 = torch.tensor([eval_c1(x) for x in train_X], **tkwargs).unsqueeze(-1)\n",
        "C2 = torch.tensor([eval_c2(x) for x in train_X], **tkwargs).unsqueeze(-1)\n",
        "\n",
        "# Initialize TuRBO state\n",
        "state = ScboState(dim, batch_size=batch_size)\n",
        "\n",
        "# Note: We use 2000 candidates here to make the tutorial run faster.\n",
        "# SCBO actually uses min(5000, max(2000, 200 * dim)) candidate points by default.\n",
        "N_CANDIDATES = 2000 if not SMOKE_TEST else 4\n",
        "sobol = SobolEngine(dim, scramble=True, seed=1)\n",
        "\n",
        "\n",
        "def get_fitted_model(X, Y):\n",
        "    likelihood = GaussianLikelihood(noise_constraint=Interval(1e-8, 1e-3))\n",
        "    covar_module = ScaleKernel(  # Use the same lengthscale prior as in the TuRBO paper\n",
        "        MaternKernel(nu=2.5, ard_num_dims=dim, lengthscale_constraint=Interval(0.005, 4.0))\n",
        "    )\n",
        "    model = SingleTaskGP(\n",
        "        X,\n",
        "        Y,\n",
        "        covar_module=covar_module,\n",
        "        likelihood=likelihood,\n",
        "    )\n",
        "    mll = ExactMarginalLogLikelihood(model.likelihood, model)\n",
        "\n",
        "    with gpytorch.settings.max_cholesky_size(max_cholesky_size):\n",
        "        fit_gpytorch_mll(mll)\n",
        "\n",
        "    return model\n",
        "\n",
        "\n",
        "while not state.restart_triggered:  # Run until TuRBO converges\n",
        "    # Fit GP models for objective and constraints\n",
        "    model = get_fitted_model(train_X, train_Y)\n",
        "    c1_model = get_fitted_model(train_X, C1)\n",
        "    c2_model = get_fitted_model(train_X, C2)\n",
        "\n",
        "    # Generate a batch of candidates\n",
        "    with gpytorch.settings.max_cholesky_size(max_cholesky_size):\n",
        "        X_next = generate_batch(\n",
        "            state=state,\n",
        "            model=model,\n",
        "            X=train_X,\n",
        "            Y=train_Y,\n",
        "            C=torch.cat((C1, C2), dim=-1),\n",
        "            batch_size=batch_size,\n",
        "            n_candidates=N_CANDIDATES,\n",
        "            constraint_model=ModelListGP(c1_model, c2_model),\n",
        "            sobol=sobol,\n",
        "        )\n",
        "\n",
        "    # Evaluate both the objective and constraints for the selected candidaates\n",
        "    Y_next = torch.tensor([eval_objective(x) for x in X_next], dtype=dtype, device=device).unsqueeze(-1)\n",
        "    C1_next = torch.tensor([eval_c1(x) for x in X_next], dtype=dtype, device=device).unsqueeze(-1)\n",
        "    C2_next = torch.tensor([eval_c2(x) for x in X_next], dtype=dtype, device=device).unsqueeze(-1)\n",
        "    C_next = torch.cat([C1_next, C2_next], dim=-1)\n",
        "\n",
        "    # Update TuRBO state\n",
        "    state = update_state(state=state, Y_next=Y_next, C_next=C_next)\n",
        "\n",
        "    # Append data. Note that we append all data, even points that violate\n",
        "    # the constraints. This is so our constraint models can learn more\n",
        "    # about the constraint functions and gain confidence in where violations occur.\n",
        "    train_X = torch.cat((train_X, X_next), dim=0)\n",
        "    train_Y = torch.cat((train_Y, Y_next), dim=0)\n",
        "    C1 = torch.cat((C1, C1_next), dim=0)\n",
        "    C2 = torch.cat((C2, C2_next), dim=0)\n",
        "\n",
        "    # Print current status. Note that state.best_value is always the best\n",
        "    # objective value found so far which meets the constraints, or in the case\n",
        "    # that no points have been found yet which meet the constraints, it is the\n",
        "    # objective value of the point with the minimum constraint violation.\n",
        "    if (state.best_constraint_values <= 0).all():\n",
        "        print(f\"{len(train_X)}) Best value: {state.best_value:.2e}, TR length: {state.length:.2e}\")\n",
        "    else:\n",
        "        violation = state.best_constraint_values.clamp(min=0).sum()\n",
        "        print(\n",
        "            f\"{len(train_X)}) No feasible point yet! Smallest total violation: \"\n",
        "            f\"{violation:.2e}, TR length: {state.length:.2e}\"\n",
        "        )"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {},
      "source": [
        "### Plot Results"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": 8,
      "metadata": {},
      "outputs": [
        {
          "data": {
            "image/png": "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q1b49NNPdc7aqd721KlTGDFiBCpUqAAnJye4uLigatWqePfdd3Hp0iUMGjTI5NjyM2HCBGzYsAGjRo1C48aNUbZsWTg6OsLR0RHlypVDr169sG3bNmzevFnndS44OBjHjx/HsGHDULFiRY0+tebo2LEjTpw4gQEDBkjHtbo9f/jhB+zZs8di37KZy5hzsG/fvti+fTvGjh2L5s2bIzw8HM7OznBwcEBISAi6d++OX3/9FQcOHNAY7agkkwlRAgbDI6IidfXqVWk0iDlz5pg9OQQREVFxZlu3SIjIKOrRCOzt7Qv1bg8REVFxwISYiDQkJibihx9+AJDTdUI9DSsREVFpxVEmiAgJCQlITk7GvXv3MH36dDx+/BgymUwa/o6IiKg0Y0JMRPjggw+wYsUKjWWjR4+WZnwiIiIqzZgQE5HEwcEBFSpUwIgRI/KdZIGIiKi04CgTRERERGTTeIfYRNnZ2bh37x7c3Nyk2a6IiIiIqPgQQuDp06cICgoyOM46E2IT3bt3D6GhodYOg4iIiIjycfv2bYSEhOhdz4TYRG5ubgCA2NhYeHt7WzmakkmlUmHXrl3o0KFDocx5byvYjuZjG1oG29F8bEPLYDtaRmlox+TkZISGhkp5mz5MiE2k7ibh5uYGd3d3K0dTMqlUKjg7O8Pd3b3EnmjFAdvRfGxDy2A7mo9taBlsR8soTe2YX/dWTsxBRERERDaNCTERERER2TQmxERERERk05gQExEREZFNY0JMRERERDaNCTERERER2TQmxERERERk05gQExEREZFNY0JMRERERDaNCTERERER2TQmxERERERk05gQExEREZFNY0JMRERERDaNCTERERER2TQmxERERERk05gQExEREZFNY0JMRERERDaNCTERERER2TQmxERERERk05gQExEREZFNY0JMRERERDaNCTERERER2TSbTogXLVqEcuXKwdHREY0aNcLx48etHRIRERERFTF7awdgLWvWrMG4ceOwePFiNGrUCF9//TU6duyIy5cvw9/f3+h6Hj58iOzs7ALv39XVFU5OTnrrFEIUuE4AcHZ2houLi851jx8/RlZWlkn1Ojo6ws3NTee6xMREqFSqAtepUqnw/PlzveuTkpKQkZFR4HoBQKFQwNPTU+e6p0+fIi0tzaR67ezs4O3trXPd8+fPkZKSYlK9MpkMvr6+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UneHOD9HjhxBzZo1ERAQIC3r2LEjRo0ahfPnz6Nu3bo6t0tPT0d6err0Ojk5GQCgUqmgUqkKN+hSSt1ubD/zsB3Nxza0DLaj+diGlsF2tIzS0I7Gxm5zCXF8fLxGMgxAeh0fH693u88//xwzZszQWh4dHQ1nZ2fLBmljoqKirB1CqcB2NB/b0DLYjuZjG1oG29EySnI7pqSkGFWuRCTEkyZNwpw5cwyWuXjxIqpUqVJoMUyePBnjxo2TXicnJyM0NBStW7eGj49Poe23NFOpVIiKikL79u2hUCisHU6JxXY0H9vQMtiO5mMbWgbb0TJKQzuqv9HPT4ES4vv378POzk7vekPrAEAmk5k0msP48eMxZMgQg2XKly9vVF2BgYE4fvy4xrL79+9L6/RRKpVQKpVayxUKRYk9SIoLtqFlsB3Nxza0DLaj+diGlsF2tIyS3I7Gxm2RcYgLm5+fH/z8/CxSV5MmTTBr1iwkJCTA398fQM5XAe7u7qhWrZpF9kFEREREJYfRCfG0adMKMw6LuXXrFh4/foxbt24hKysLp0+fBgBUrFgRrq6u6NChA6pVq4aBAwfiiy++QHx8PKZMmYIxY8bovANMRERERKVbqUuIp06dihUrVkiv1aNGREdHIzIyEnZ2dti2bRtGjRqFJk2awMXFBYMHD8bMmTOtFTIRERERWVGJeKiuIJYvX47ly5cbLBMWFobt27cXTUBEREREVKyVuok5iIiIiIgKggkxEREREdk0JsREREREZNOYEBMRERGRTWNCTEREREQ2jQkxEREREdk0JsREREREZNOYEBMRERGRTWNCTEREREQ2jQkxEREREdk0JsREREREZNOYEBMRERGRTWNCTEREREQ2jQkxEREREdk0JsREREREZNOYEBMRERGRTWNCTEREREQ2jQkxEREREdk0JsREREREZNOYEBMRERGRTWNCTEREREQ2jQkxEREREdk0JsREREREZNOYEBMRERGRTWNCTEREREQ2jQkxEREREdk0JsREREREZNOYEBMRERGRTWNCTEREREQ2jQkxEREREdk0JsREREREZNOYEBMRERGRTWNCTEREREQ2jQkxEREREdk0JsREREREZNOYEBMRERGRTWNCTEREREQ2jQkxEREREdk0JsREREREZNOYEBMRERGRTWNCTEREREQ2jQkxEREREdk0JsRE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",
            "text/plain": [
              "<Figure size 800x600 with 1 Axes>"
            ]
          },
          "metadata": {},
          "output_type": "display_data"
        }
      ],
      "source": [
        "import matplotlib.pyplot as plt\n",
        "import numpy as np\n",
        "from matplotlib import rc\n",
        "\n",
        "%matplotlib inline\n",
        "\n",
        "fig, ax = plt.subplots(figsize=(8, 6))\n",
        "\n",
        "score = train_Y.clone()\n",
        "# Set infeasible to -inf\n",
        "score[~(torch.cat((C1, C2), dim=-1) <= 0).all(dim=-1)] = float(\"-inf\")\n",
        "fx = np.maximum.accumulate(score.cpu())\n",
        "plt.plot(fx, marker=\"\", lw=3)\n",
        "\n",
        "plt.plot([0, len(train_Y)], [fun.optimal_value, fun.optimal_value], \"k--\", lw=3)\n",
        "plt.ylabel(\"Function value\", fontsize=18)\n",
        "plt.xlabel(\"Number of evaluations\", fontsize=18)\n",
        "plt.title(\"10D Ackley with 2 outcome constraints\", fontsize=20)\n",
        "plt.xlim([0, len(train_Y)])\n",
        "plt.ylim([-15, 1])\n",
        "\n",
        "plt.grid(True)\n",
        "plt.show()"
      ]
    },
    {
      "cell_type": "code",
      "execution_count": null,
      "metadata": {},
      "outputs": [],
      "source": []
    }
  ],
  "metadata": {
    "kernelspec": {
      "display_name": "Python 3 (ipykernel)",
      "language": "python",
      "name": "python3"
    },
    "language_info": {
      "codemirror_mode": {
        "name": "ipython",
        "version": 3
      },
      "file_extension": ".py",
      "mimetype": "text/x-python",
      "name": "python",
      "nbconvert_exporter": "python",
      "pygments_lexer": "ipython3",
      "version": "3.9.13"
    },
    "vscode": {
      "interpreter": {
        "hash": "9beb4c3e6521665a47c2b1e65f245d1b2309f4194f15ed6955f5e52622a9d29e"
      }
    }
  },
  "nbformat": 4,
  "nbformat_minor": 2
}
